#!/usr/bin/env python
# -*- coding: utf-8 -*-

# lazy.py

# Copyright 2009-2010 Diego Hansen Hahn (aka DiegoHH) <diegohh90 [at] hotmail [dot] com>

# lazynds is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License.

# lazynds is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with lazynds. If not, see <http://www.gnu.org/licenses/>.

__author__ = "Diego Hansen Hahn"
__version__ = "v2.0.2"

import heapq
import os
import sys
import shutil

import pygtk
pygtk.require('2.0')

import gtk
import gobject

import bios
import widgets
import areas
import structure
import pluginmanager

import dialogs
from images import images, palettes

def filterlist(**kwargs):
    filterlist = []
    for filter_name in kwargs:
        filter = gtk.FileFilter()
        filter.set_name(kwargs[filter_name][0])
        filter.add_pattern(kwargs[filter_name][1])
        filterlist.append((filter_name, filter))
    return filterlist

def populate_combo_box(cb, items):
    model = gtk.ListStore(gobject.TYPE_STRING)
    for x in items:
        model.append([x])
    cb.set_model(model)
    cell = gtk.CellRendererText()
    cb.pack_start(cell, True)
    cb.add_attribute(cell, 'text', 0)

class Main:
    def __init__(self):
        if hasattr(sys, 'frozen'):
            self.local_path = os.path.realpath(os.path.dirname(sys.executable))
        else:
            self.local_path = os.path.realpath(os.path.dirname(__file__))

        self.wtree_path = os.path.join(self.local_path, 'editor.ui')

        self.builder = gtk.Builder()
        self.builder.add_from_file(self.wtree_path)
        self.builder.connect_signals(self)

        self.main_window = self.builder.get_object('main_window')
        self.main_window.connect('destroy', gtk.main_quit)

        self.layout = self.builder.get_object('main_layout')

        self.__init_compressions()

        self.__init_builder_widgets()
        self.__init_control_area()
        self.__init_palette_area()
        self.__init_color_area()
        self.__init_graphic_area()

        self.main_window.set_default_size(900,600)
        self.main_window.show_all()

    def __init_compressions(self):
        pluginmanager.load_plugins('plugins.list')
        answer = pluginmanager.trigger_event('PLUGIN-REGISTER')

        self.plugins = {}
        for x in answer:
            self.plugins.update(x)

        self.pluginlist = widgets.DecoderList()
        self.pluginlist.append(self.plugins)
        self.pluginlist.set_name(col0 = 'Plugins')
        self.pluginlist.make()

        self.complist = widgets.DecoderList()
        self.complist.append({'LZSS':0x10, 'RLE':0x30, 'Huffman':0x20})
        self.complist.set_name(col0 = 'Rotinas da BIOS')
        self.complist.make()

        self.builder.get_object('scrolledwindow1').add(self.pluginlist)
        self.builder.get_object('scrolledwindow2').add(self.complist)

    def __init_builder_widgets(self):
        self.data_control = widgets.BuilderContainer(self.layout, self.builder.get_object('vbox1'), 20,20)
        self.image_control = widgets.BuilderContainer(self.layout, self.builder.get_object('vbox3'), 20,280)
        self.palette_area = widgets.BuilderContainer(self.layout, self.builder.get_object('vbox8'), 690,20)

    def __init_control_area(self):
        self.img_index_range = self.builder.get_object('img_index')
        self.sb_id_1 = self.img_index_range.connect('value-changed', self.on_img_index_range_value_changed)

        self.img_width_range = self.builder.get_object('img_width')
        self.sb_id_2 = self.img_width_range.connect('value-changed', self.on_img_width_range_value_changed)

        self.img_offset_range = widgets.HexSpinButton()
        self.img_offset_range.set_size_request(60,-1)
        self.builder.get_object('hbox4').pack_start(self.img_offset_range, True, True)


    def __init_palette_area(self):
        self.palette = palettes.Palette()
        self.palette_widget = palettes.PaletteWidget(self.palette)

        self.palette_widget.set_left_button_event(True)
        self.palette_widget.connect('one-click', self.on_palette_widget_one_click_event)

        # Adiciona 256 blocos de cores na paleta
        for i, y in enumerate(range(1, 161, 10)):
            for j, x in enumerate(range(1, 161, 10)):
                self.palette_widget.add_color(palettes.Colors(((i * 16) + (j + 1)), (0, 0, 0), (x, y)))

        box_mode_list = self.builder.get_object('box_mode_list')
        populate_combo_box(box_mode_list, [' Unidimensional', ' Bidimensional'])
        box_mode_list.set_active(0)
        box_mode_list.connect('changed', self.on_box_mode_list_changed)

        box_codec_list = self.builder.get_object('box_codec_list')
        populate_combo_box(box_codec_list, [' 1BPP Linear Reverso', ' 2BPP Linear Reverso', ' 4BPP Linear Reverso', ' 8BPP Linear'])
        box_codec_list.set_active(2)
        box_codec_list.connect('changed', self.on_box_codec_list_changed)

        self.box_palette_list = self.builder.get_object('box_palette_list')
        populate_combo_box(self.box_palette_list, [' Escala de Cinza', ' Paleta Interna', ' Paleta Externa'])
        self.box_palette_list.set_active(0)
        self.box_palette_list.connect('changed', self.on_box_palette_list_changed)

        self.pal_index_range = self.builder.get_object('pal_index_range')
        self.pal_index_range.set_adjustment(gtk.Adjustment(0, 0, 15, 1, 1, 0))
        self.pal_index_range.connect('changed', self.on_pal_index_range_value_changed)

        self.pal_offset_range = widgets.HexSpinButton()
        self.pal_offset_range.set_size_request(60,-1)
        self.builder.get_object('hbox9').pack_start(self.pal_offset_range, True, True)
        self.pal_offset_range.connect('value-changed', self.on_pal_offset_range_value_changed)

    def __init_color_area(self):
        self.builder.get_object('vwp_palette').add(self.palette_widget)
        self.red_range = self.builder.get_object('red_range')
        self.blue_range = self.builder.get_object('blue_range')
        self.green_range = self.builder.get_object('green_range')
        self.color_label = self.builder.get_object('color_label')

        self.sg_id_1 = self.red_range.connect('value-changed', self.on_color_range_value_changed)
        self.sg_id_2 = self.green_range.connect('value-changed', self.on_color_range_value_changed)
        self.sg_id_3 = self.blue_range.connect('value-changed', self.on_color_range_value_changed)

        self.transparent = False

    def __init_graphic_area(self):
        self.img_container = areas.ImageContainer((8, 8))
        self.img_container.set_palette(self.palette)

        self.img_widget = areas.GraphicWidget()
        self.img_widget.add_container(self.img_container)
        self.image_area = widgets.ChildContainer(self.layout, self.img_widget, 230, 20, True)

    def __init_rom_parameters(self):
        # Monta a lista ordenada de menor a maior endereço
        self.decoder_results = []
        helper = []
        for data in self.bios_results:
            heapq.heappush(helper, data)

        for plugin in self.plugins_results:
            for data in plugin:
                heapq.heappush(helper, data)

        while helper:
            self.decoder_results.append(heapq.heappop(helper))

        self.lazyaddress = self.decoder_results[0][0]
        if self.decoder_results[0][1] in (0x10, 0x24, 0x28, 0x30):
            self.lazybuffer = self.bios_functions.uncompress(*self.decoder_results[0])
        else:
            self.lazybuffer = pluginmanager.trigger_event(self.decoder_results[0][1] + '-DECODER',
                                                          file = self.lazyfile,
                                                          address = self.decoder_results[0][0])[0]

        self.lazywidth = 32

        self.img_index_range.set_adjustment(gtk.Adjustment(0, 0, (len(self.decoder_results)-1), 1, 1, 0))
        self.img_offset_range.set_adjustment(gtk.Adjustment(0, 0, (os.path.getsize(self.lazyfile.name)-1), 1, 1, 0))
        self.img_offset_range.set_value(self.lazyaddress)
        self.img_width_range.set_adjustment(gtk.Adjustment(0, 1, 99, 1, 1, 0))
        self.img_width_range.set_value(self.lazywidth)

        self.pal_offset_range.set_adjustment(gtk.Adjustment(0, 0, (os.path.getsize(self.lazyfile.name)-1), 1, 512, 0))
        self.pal_offset_range.set_value(0)

    def on_main_window_delete_event(self, widget, event):
        if hasattr(self, 'lazyfile'):
            self.lazyfile.close()
            os.unlink(self.lazyfile.name)
        if hasattr(self, 'lazypalette'):
            self.lazypalette.close()

        return False

# Gerenciamento dos parâmetros da imagem:
    def on_img_index_range_value_changed(self, widget):
        index = widget.get_value_as_int()

        self.lazyaddress = self.decoder_results[index][0]
        if self.decoder_results[index][1] in (0x10, 0x24, 0x28, 0x30):
            self.lazybuffer = self.bios_functions.uncompress(*self.decoder_results[index])
        else:
            self.lazybuffer = pluginmanager.trigger_event(self.decoder_results[index][1] + '-DECODER',
                                                          file = self.lazyfile,
                                                          address = self.decoder_results[index][0])[0]

        self.img_offset_range.set_value(self.lazyaddress)
        self.img_container.set_buffer(self.lazybuffer, self.lazywidth)
        self.img_container.buffer2image()
        self.img_widget.refresh()

    def on_img_width_range_value_changed(self, widget):
        self.lazywidth = widget.get_value_as_int()
        if hasattr(self, 'lazybuffer'):
            setattr(self.img_container, 'width', self.lazywidth)
            self.img_container.buffer2image()
            self.img_widget.refresh()

# Gerenciamento do controle de imagem
    def on_box_mode_list_changed(self, widget):
        self.img_container.toggle_mode()
        if hasattr(self, 'lazybuffer'):
            self.img_container.buffer2image()
            self.img_widget.refresh()

    def on_box_codec_list_changed(self, widget):
        self.palette.set_codec([1,2,4,8][widget.get_active()])
        self.palette.set_palette_mode(self.palette.mode)

        disp_palettes = 256 / 2**self.palette.get_codec() - 1
        self.pal_index_range.set_adjustment(gtk.Adjustment(0,0,disp_palettes,1,1,0))
        self.pal_index_range.set_value(0)

        if hasattr(self, 'lazybuffer'):
            self.draw_new_palette()
            self.img_container.buffer2image()
            self.img_widget.refresh()

    def on_box_palette_list_changed(self, widget):
        if not hasattr(self, 'lazyfile'):
            return None

        mode = widget.get_active()
        if mode == self.palette.colors_gray:
            self.pal_offset_range.set_sensitive(False)
            self.palette.generate_grayscale_pal()
            self.palette.set_palette_mode(self.palette.colors_gray)
        elif mode == self.palette.colors_romfile:
            self.pal_offset_range.set_sensitive(True)
            address = int(self.pal_offset_range.get_text(), 16)
            self.palette.generate_pal_from_rom(self.lazyfile, address)
            self.palette.set_palette_mode(self.palette.colors_romfile)
        elif mode == self.palette.colors_extern:
            if not hasattr(self, 'lazypalette'):
                return None
            self.pal_offset_range.set_sensitive(False)
            self.palette.generate_pal_from_extern(self.lazypalette)
            self.palette.set_palette_mode(self.palette.colors_extern)

        self.draw_new_palette()
        self.pal_index_range.set_value(0)
        if hasattr(self, 'lazybuffer'):
            self.img_container.buffer2image()
            self.img_widget.refresh()

    def on_pal_index_range_value_changed(self, widget):
        index = widget.get_value_as_int()
        self.palette.set_palette_number(index)
        if not hasattr(self, 'lazybuffer'):
            return None
        self.img_container.buffer2image()
        self.img_widget.refresh()

    def on_pal_offset_range_value_changed(self, widget):
        if not hasattr(self, 'lazyfile'):
            return None

        address = int(widget.get_text(), 16)
        self.palette.generate_pal_from_rom(self.lazyfile, address)
        self.palette.set_palette_mode(self.palette.colors_romfile)
        self.draw_new_palette()
        self.on_palette_widget_one_click_event(None)

        if not hasattr(self, 'lazybuffer'):
            return None

        self.img_container.buffer2image()
        self.img_widget.refresh()


# Gerenciamento do widget de cores:
    def on_palette_widget_one_click_event(self, widget):
        if not hasattr(self, 'lazyfile'):
            return None

        if not self.palette_widget.previous_color:
            return None

        self.red_range.set_sensitive(True)
        self.green_range.set_sensitive(True)
        self.blue_range.set_sensitive(True)

        rgb = self.palette_widget.previous_color.get_color()

        # Desconectar os sinais para não ser chamado a atualização da paleta
        # durante a atualização dos valores dos spinbuttons
        self.red_range.disconnect(self.sg_id_1)
        self.green_range.disconnect(self.sg_id_2)
        self.blue_range.disconnect(self.sg_id_3)

        self.red_range.set_value(int(rgb[0] * 31))
        self.green_range.set_value(int(rgb[1] * 31))
        self.blue_range.set_value(int(rgb[2] * 31))
        self.color_label.set_text('%04Xh'
                                  % (((int(rgb[2] * 31) << 10) | (int(rgb[1] * 31) << 5) | int(rgb[0] * 31) ) & 0x7FFF))

        self.sg_id_1 = self.red_range.connect('value-changed', self.on_color_range_value_changed)
        self.sg_id_2 = self.green_range.connect('value-changed', self.on_color_range_value_changed)
        self.sg_id_3 = self.blue_range.connect('value-changed', self.on_color_range_value_changed)

    def on_color_range_value_changed(self, widget):
        rgb = (self.red_range.get_value()/31.0,
               self.green_range.get_value()/31.0,
               self.blue_range.get_value()/31)
        self.palette_widget.previous_color.set_color(rgb)
        self.palette_widget.refresh()
        self.palette.change_color(rgb, self.palette_widget.previous_color.id)

        self.color_label.set_text('%04Xh'
                                  % (((int(rgb[2] * 31) << 10) | (int(rgb[1] * 31) << 5) | int(rgb[0] * 31) ) & 0x7FFF))
        self.img_container.buffer2image()
        self.img_widget.refresh()

    def on_chk_transparent_color_toggled(self, widget):
        if not hasattr(self, 'lazybuffer'):
            return None
        if widget.get_active():
            self.transparent = True
            self.img_container.transparent = True
        else:
            self.transparent = False
            self.img_container.transparent = False
        self.img_container.buffer2image()
        self.img_widget.refresh()

    def draw_new_palette(self):
        ''' Povoa a paleta com as cores '''
        ctrl = 0
        for color in self.palette_widget:
            color.set_color(self.palette[ctrl])
            ctrl += 1

        self.palette.set_palette_number(0)
        self.palette_widget.refresh()

# Gerenciamento de dados
    def on_open_file_item_activate(self, widget):
        _filterlist = filterlist(NDS = ('Roms Nintendo DS (*.nds)', '*.nds'),
                                 GBA = ('Roms Gameboy Advance (*.gba)', '*.gba'))

        if not hasattr(self, 'filefolder'):
            self.filefolder = self.local_path

        filename, self.filefolder, usedfilter = dialogs.FileChooser(self.main_window, self.filefolder, _filterlist, 'open')
        if not filename:
            return None
        elif os.name == 'nt':
            filename = filename.encode('windows-1252')

        path, name = os.path.split(filename)
        backupname = os.path.join(self.local_path, (name+'.backup'))
        shutil.copy(filename, backupname)

        file = open(backupname, 'r+b')

        if not structure.isNds(file):
            if not structure.isGba(file):
                type = None
            else:
                type = 'gba'
        else:
            type = 'nds'

        self.bios_functions = bios.bios(file)

        dialog = dialogs.Scan(self.builder, self.main_window, self.bios_functions, self.complist, self.pluginlist)
        self.bios_results, self.plugins_results = dialog.run()

        if not isinstance(self.bios_results, list) and not isinstance(self.plugins_results, list):
            # Não modifica nada
            return None

        self.lazyfile = file
        self.lazyfilename = filename
        self.lazybackupname = backupname
        self.lazytype = type

        self.__init_rom_parameters()

        self.img_container.set_buffer(self.lazybuffer, 32)
        self.img_container.buffer2image()
        self.img_widget.refresh()
        self.draw_new_palette()


    def on_btn_export_data_clicked(self, widget):
        if not hasattr(self, 'lazybuffer'):
            return None
        # Fazer com que os argumentos sejam ordenados conforme são adicionados
        _filterlist = filterlist(BMP = ('Imagem de bitmap (*.bmp)', '*.bmp'),
                                 PNG = ('Imagem PNG (*.png)', '*.png'),
                                 RAW = ('Arquivo de Dados (*.raw)', '*.raw'),
                                 ALL = ('Todos os Arquivos (*.*)', '*.*'))

        if not hasattr(self, 'datafolder'):
            self.datafolder = self.local_path

        filename, self.datafolder, usedfilter = dialogs.FileChooser(self.main_window, self.datafolder, _filterlist, 'save')

        if not filename:
            return None
        elif os.name == 'nt':
            filename = filename.encode('windows-1252')

        for filter in _filterlist:
            if filter[1].get_name() == usedfilter.get_name():
                extension = filter[0]

        if (extension != 'ALL') and (filename.split('.')[-1].lower() != extension.lower()):
            filename += '.' + extension.lower()

        output = open(filename, 'wb')
        if extension in ('BMP', 'PNG'):
            w = images.Writer(self.img_container.get_size(), self.palette.get_codec(), self.palette, self.transparent, 1)
            w.write(output, self.lazybuffer, extension)
        else:
            self.lazybuffer.tofile(output)

        output.close()

        pass

    def on_btn_open_palette_file_clicked(self, widget):
        _filterlist = filterlist(PAL = ('Arquivo de paleta (*.pal)', '*.pal'),
                                 ALL = ('Todos os Arquivos (*.*)', '*.*'))

        if not hasattr(self, 'palettefolder'):
            self.palettefolder = self.local_path

        filename, self.palettefolder, usedfilter = dialogs.FileChooser(self.main_window, self.palettefolder, _filterlist, 'open')

        if not filename:
            return None
        elif os.name == 'nt':
            filename = filename.encode('windows-1252')

        self.lazypalette = open(filename, 'rb')
        self.palette.generate_pal_from_extern(self.lazypalette)

        if not hasattr(self, 'lazybuffer'):
            return None

        if self.box_palette_list.get_active() == self.palette.colors_extern:
            self.palette.set_palette_mode(self.palette.colors_extern)
            self.draw_new_palette()
            self.img_container.buffer2image()
            self.img_widget.refresh()


def Run():
    import psyco
    psyco.full()

    gtk.gdk.threads_init()
    gtk.gdk.threads_enter()
    try:
        gtk.main()
    finally:
        gtk.gdk.threads_leave()

if __name__ == '__main__':
    Main()
    Run()